package cn.itcast;

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.LinkedList;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;

/*
贪吃蛇游戏的实现

ctrl + f12: 查看类中的成员(成员变量, 成员方法)
 */
public class SnakeGame extends JFrame {

    public Container container;

    public static final int IMAGE_SIZE = 15;
    // 地图的列数
    public static final int COLUMN_COUNT = 50;
    // 地图的行数
    public static final int ROW_COUNT = 25;
    // 蛇的坐标数据
    public LinkedList<Point> snake = new LinkedList<>();
    public LinkedList<JLabel> snakeImageList = new LinkedList<>();

    // 右方向
    public static final int DIRECTION_RIGHT = 1;
    // 左方向
    public static final int DIRECTION_LEFT = -1;
    // 上方向
    public static final int DIRECTION_UP = 2;
    // 下方向
    public static final int DIRECTION_DOWN = -2;
    // 蛇的当前方向(会变化,默认右)
    public static int currentDirection = DIRECTION_RIGHT;
    // 食物的坐标数据
    public Point food;
    public JLabel foodImage;
    // 保存蛇吃掉的食物数量
    public static int eatFoodCount = 0;
    // 保存蛇移动步数
    public static int numberOfSnakeMoves = 0;

    // 自动移动的时间
    public int periodTime = 300;

    public SnakeGame() {
        addKeyListener();
        initFrame();
        initBackground();
        initSnake();
        // 初始化食物
        createFood();
        // 蛇的自动移动
        autoMove();
    }

    public void addKeyListener() {
        addKeyListener(new KeyAdapter() {
            @Override // 键盘按下会调用这个方法
            public void keyPressed(KeyEvent e) {
                // 获取用户按下的按键
                int keyCode = e.getKeyCode();
                switch (keyCode) {
                    case KeyEvent.VK_UP:
                    case KeyEvent.VK_W:
                        move(DIRECTION_UP);
                        break;
                    case KeyEvent.VK_RIGHT:
                    case KeyEvent.VK_D:
                        move(DIRECTION_RIGHT);
                        break;
                    case KeyEvent.VK_DOWN:
                    case KeyEvent.VK_S:
                        move(DIRECTION_DOWN);
                        break;
                    case KeyEvent.VK_LEFT:
                    case KeyEvent.VK_A:
                        move(DIRECTION_LEFT);
                        break;
                }
            }
        });
    }

    private void initBackground() {
        // 给地图生成数据
        for (int row = 0; row < ROW_COUNT; row++) {
            // 索引 row = 0, 1, 2, 3, ... 8
            for (int column = 0; column < COLUMN_COUNT; column++) {
                // 索引 column = 0, 1, 2, 3, ... 32
                // 什么是空格,什么时候是*
                if (column == 0 || column == COLUMN_COUNT - 1 || row == 0 || row == ROW_COUNT - 1) {
                    JLabel body = new JLabel(new ImageIcon("D:/work/workspace/class_193/SnakeGame/resources/wall.png"));
                    body.setBounds(column * IMAGE_SIZE, row * IMAGE_SIZE, IMAGE_SIZE, IMAGE_SIZE);
                    container.add(body);
                }
            }
        }
        container.repaint();
    }

    private void initFrame() {
        // 设置标题
        this.setTitle("贪吃蛇");
        // 设置大小
        this.setSize((COLUMN_COUNT + 1) * IMAGE_SIZE, (ROW_COUNT + 1) * IMAGE_SIZE + 20);
        // 设置关闭模式
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        // 设置窗口无法进行调节
        this.setResizable(false);
        // 界面居中
        this.setLocationRelativeTo(null);
        this.setVisible(true);
        // 获取界面中的隐藏容器，以后直接用无需再次调用方法获取了
        container = this.getContentPane();
        // 取消内部默认的居中放置
        container.setLayout(null);
        // 设置背景颜色
        container.setBackground(Color.LIGHT_GRAY);
    }

    /**
     * 初始化蛇
     */
    private void initSnake() {
        // 计算地图的中心点
        int centerX = COLUMN_COUNT / 2;
        int centerY = ROW_COUNT / 2;

        // 创建蛇的三个点
        Point head = new Point(centerX + 1, centerY);
        Point body1 = new Point(centerX, centerY);
        Point body2 = new Point(centerX - 1, centerY);

        // 把这三个点保存到LinkedList集合中
        snake.add(head);
        snake.add(body1);
        snake.add(body2);

        // 把LinkedList集合中蛇的坐标数据刷新到地图上
        flushSnake();
    }

    /**
     * 把LinkedList集合中蛇的坐标数据刷新到地图上
     */
    public void flushSnake() {
        for (int i = 0; i < snakeImageList.size(); i++) {
            JLabel snakeImage = snakeImageList.get(i);
            container.remove(snakeImage);
        }

        // 获取蛇的头部,刷新到地图上
        Point head = snake.getFirst();
        JLabel headImage = new JLabel(new ImageIcon("D:/work/workspace/class_193/SnakeGame/resources/head_up.png"));
        headImage.setBounds(head.x * IMAGE_SIZE, head.y * IMAGE_SIZE, IMAGE_SIZE, IMAGE_SIZE);
        container.add(headImage);
        snakeImageList.add(headImage);

        // 遍历获取蛇的身体,刷新到地图上, 要从1索引开始, 0索引是蛇的头部,前面已经处理啦
        for (int i = 1; i < snake.size(); i++) {
            Point body = snake.get(i);
            JLabel bodyImage = new JLabel(new ImageIcon("D:/work/workspace/class_193/SnakeGame/resources/body.png"));
            bodyImage.setBounds(body.x * IMAGE_SIZE, body.y * IMAGE_SIZE, IMAGE_SIZE, IMAGE_SIZE);
            container.add(bodyImage);
            snakeImageList.add(bodyImage);
        }
        container.repaint();
    }

    /**
     * 蛇的自动移动
     */
    public void autoMove() {
        // 使用定时器, 每隔1秒移动一次
        Timer timer = new Timer();
        // timer.schedule(TimerTask task, long delay, long period);
        // timer.schedule(要执行的任务, 第一次多久后执行, 每隔多久重复执行一次);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                // 要执行的任务, 让蛇移动
                move(currentDirection);
            }
        }, 3000, periodTime);
    }

    /**
     * 蛇的移动
     *
     * @param newDirection 移动方向
     */
    public void move(int newDirection) {
        // 蛇不能反向移动
        if (currentDirection + newDirection == 0) {
            System.out.println("不能反反向移动!");
            // 是反向, 结束方法
            return;
        }
        // 蛇的移动数量+1
        numberOfSnakeMoves++;

        // 把蛇的当前方向设置为最新方向
        currentDirection = newDirection;

        // 蛇移动
        // 1.在蛇头的右边添加一个点作为蛇头
        Point head = snake.getFirst();
        switch (currentDirection) {
            case DIRECTION_RIGHT:
                snake.addFirst(new Point(head.x + 1, head.y));
                break;
            case DIRECTION_DOWN:
                snake.addFirst(new Point(head.x, head.y + 1));
                break;
            case DIRECTION_LEFT:
                snake.addFirst(new Point(head.x - 1, head.y));
                break;
            case DIRECTION_UP:
                snake.addFirst(new Point(head.x, head.y - 1));
                break;
        }

        if (isEatFood()) {
            // 吃到食物, 创建新的食物
            createFood();
            // 吃到食物的数量+1
            eatFoodCount++;
        } else {
            // 没有吃到食物
            // 3.删除蛇尾
            snake.removeLast();
        }

        // 游戏是否结束
        if (isGameOver()) {
            System.out.println("游戏结束!");
            // 退出Java虚拟机, 程序结束啦!
            System.exit(0);
        }

        // 把蛇的数据刷新到地图上
        flushSnake();
    }

    /**
     * 判断游戏是否结束
     *
     * @return true游戏结束, false游戏不结束
     */
    public boolean isGameOver() {
        Point head = snake.getFirst();
        // 看蛇头的坐标是否在地图的*上面
        if (head.x == 0 || head.x == COLUMN_COUNT - 1 || head.y == 0 || head.y == ROW_COUNT - 1) {
            // 蛇头在墙壁上, 返回true, 结束游戏
            System.out.println("蛇头在墙壁上, 结束游戏");
            return true;
        }

        // 看蛇是否吃到自己
        // 遍历获取蛇的每个点,判断蛇头是否在身体上
        for (int i = 1; i < snake.size(); i++) {
            Point body = snake.get(i);
            if (head.equals(body)) {
                // 蛇头在身体上, 返回true, 结束游戏
                System.out.println("蛇头在身体上, 结束游戏");
                return true;
            }
        }

        return false;
    }

    /**
     * 判断是否吃到食物
     *
     * @return true吃到食物, false没有吃到食物
     */
    public boolean isEatFood() {
        Point head = snake.getFirst();

        // if (head.x == food.x && head.y == food.y) {
        //     // 如果蛇头和食物坐标相同, 吃到食物
        //     return true;
        // } else {
        //     // 否则, 没有吃到食物
        //     return false;
        // }

        // return head.x == food.x && head.y == food.y;
        return head.equals(food); // 判断两个点是否相同, 相同返回true, 不相同返回false
    }

    /**
     * 初始化食物
     */
    private void createFood() {
        // 创建随机数
        Random r = new Random();

        // 不能让食物随机到蛇的身体上
        while (true) {
            // 随机X坐标 [1, 31] -> -1 -> [0, 30] + 1
            int x = r.nextInt(COLUMN_COUNT - 2) + 1;
            // 随机Y坐标 [1, 7] -> -1 -> [0, 6] + 1
            int y = r.nextInt(7) + 1;

            // 创建成Point
            food = new Point(x, y);

            // 假设食物不在蛇身上
            boolean flag = false;
            // 食物的坐标在蛇身上,需要重新生成
            // 食物的坐标不在蛇身上,可以使用
            // 遍历蛇的坐标点
            for (int i = 0; i < snake.size(); i++) {
                Point snakePoint = snake.get(i);
                // 蛇身上的点和食物的点相同, 食物在蛇身上
                if (snakePoint.x == food.x && snakePoint.y == food.y) {
                    // 食物在蛇身上
                    flag = true;
                    break;
                }
            }

            if (flag) {
                // 食物在蛇身上
                System.out.println("运气很好, 食物在蛇身上, 重新生成");
            } else {
                // 食物不在蛇身上, 可以使用这个点, 不用再生成啦
                break;
            }
        }

        if (foodImage != null) {
            container.remove(foodImage);
        }

        // 把食物的坐标数据刷新到地图上
        foodImage = new JLabel(new ImageIcon("D:/work/workspace/class_193/SnakeGame/resources/food.png"));
        foodImage.setBounds(food.x * IMAGE_SIZE, food.y * IMAGE_SIZE, IMAGE_SIZE, IMAGE_SIZE);
        container.add(foodImage);

        container.repaint();
    }
}
